package com.feature50.makeover;

import com.feature50.makeover.chart.*;
import com.feature50.makeover.ui.CandylandScrollbarUI;
import com.feature50.makeover.ui.MiniBlackComboUI;
import com.feature50.makeover.utils.FontResolver;
import com.feature50.makeover.utils.ImageUtils;
import com.feature50.makeover.utils.RandomUtils;
import com.jgoodies.forms.factories.Borders;
import com.jgoodies.forms.layout.CellConstraints;
import com.jgoodies.forms.layout.FormLayout;
import org.apache.batik.ext.awt.LinearGradientPaint;
import org.jdesktop.animation.timing.Animator;
import org.jdesktop.swingx.JXPanel;
import org.jdesktop.swingx.painter.gradient.BasicGradientPainter;
import org.jfree.data.category.CategoryDataset;
import org.jfree.data.category.DefaultCategoryDataset;

import javax.swing.*;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableModel;
import java.awt.*;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.geom.Point2D;
import java.awt.image.BufferedImage;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;

public class DashboardPanel extends AppPanel {
    private static final CellConstraints CC = new CellConstraints();

    private String[][] IO_DATA =  {
            { "2663", "EBAY", "MAC'S TUNA SHACK" },
            { "2642", "EBAY", "MARK'S AUTO BODY" },
            { "2619", "YAHOO! SEARCH", "MARK'S AUTO BODY" },
            { "2618", "YAHOO! SEARCH", "LUNCH BOXES R US" }
    };

    private String[][] AUDIT_DATA =  {
            { "3/15/08", "ADAGE.COM", "MAB 14 Jan 08", "Feb-08" },
            { "3/18/08", "GOOGLE", "MACS 14 Jan 08", "Feb-08" },
            { "4/15/08", "ALLBUSINESS.COM", "LBRS 14 Jan 08", "Mar-08" },
            { "5/12/08", "ALLBUSINESS.COM", "LBRS 14 Jan 08", "Apr-08" },
    };

    private CategoryDataset perfModel;
    private BarChart perfChart;
    private BarChart campaignChart;
    private JPanel panel2;
    private JLabel alertLabel;
    private Animator throbAnimator;
    private ThrobberThread thread;
    private BufferedImage alertImage;
    private BarChart auditChart;

    private Map<Comparable, DefaultCategoryDataset> campaignData = new HashMap<Comparable, DefaultCategoryDataset>();
    private int campaignIdCounter = 1001;
    private JComponent formComponent;

    public DashboardPanel() {
        formComponent = createFormComponent();
        setLayout(new GridLayout(1, 1));
        add(formComponent);

        createAndLayoutComponents(formComponent);
        getData();
        initializeComponents(formComponent);
        attachListenersToComponents(formComponent);
    }

    protected JComponent createFormComponent() {
        JXPanel background = new JXPanel();
        background.setBackgroundPainter(new BasicGradientPainter(new Point2D.Double(0, 0), Color.BLACK, new Point2D.Double(0, 1d), new Color(33, 35, 42)));
        return background;
    }

    protected void getData() {
        DefaultCategoryDataset model = new DefaultCategoryDataset();
        model.addValue(-.99f, "Row", "Apple");
        model.addValue(-.46f, "Row", "Audi USA");
        model.addValue(-.35f, "Row", "Microsoft");
        model.addValue(-.31f, "Row", "Chevrolet");
        model.addValue(-.12f, "Row", "Checker Auto");
        model.addValue(-.10f, "Row", "Yellow Mug");
        model.addValue(.10f, "Row", "Levi Strauss");
        model.addValue(.21f, "Row", "Maceys");
        model.addValue(.23f, "Row", "Adode");
        model.addValue(.33f, "Row", "Fred Meyer");
        model.addValue(.43f, "Row", "Nabisco");
        model.addValue(.44f, "Row", "Sears");
        model.addValue(.63f, "Row", "Allen Bros");
        model.addValue(.69f, "Row", "Giant Bicycle");
        model.addValue(.75f, "Row", "Panic Software");
        model.addValue(.78f, "Row", "Bravo Computer");
        model.addValue(.79f, "Row", "Brownells");
        model.addValue(.81f, "Row", "Borders");
        model.addValue(.84f, "Row", "Nike");
        model.addValue(.90f, "Row", "Barnes & Noble");
        model.addValue(.98f, "Row", "Maxfields");
        model.addValue(.98f, "Row", "Global Travel");
        model.addValue(.99f, "Row", "Land's End");
        model.addValue(.99f, "Row", "Devware Inc.");
        this.perfModel = model;
    }

    protected void createAndLayoutComponents(Container container) {
        JPanel panel = (JPanel) container;

        FormLayout layout = new FormLayout("fill:default:grow, 10px, fill:default:grow", "default, 5px, fill:default:grow, 5px, fill:default:grow");
        panel.setLayout(layout);

        layout.setColumnGroups(new int[][] { new int[] { 1, 3 } });
        layout.setRowGroups(new int[][] { new int[] { 3, 5 } });

        panel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));

        JComponent auditPanel = createAuditPanel();
        container.add(auditPanel, CC.xyw(1, 5, 3));

        JPanel panelSummary = new JPanel();
        panelSummary.setOpaque(false);
        FormLayout slayout = new FormLayout("fill:default:grow", "default, 5px, fill:default:grow");
        panelSummary.setLayout(slayout);
        container.add(panelSummary, new CellConstraints().xy(1, 3));

        perfChart = ChartFactory.createBarChart(perfModel, BarChartBars.ValueAxisPosition.Center, true);
        perfChart.getBars().setColorResolver(new ColorResolver() {
            private java.util.List<String> keys = Arrays.asList("apple", "audi usa", "microsoft", "chevrolet", "maxfields", "nike", "sears", "checker auto", "yellow mug");
            public Color getColor(Comparable rowKey, Comparable columnKey, int rowIndex, float value) {
                if (keys.contains(columnKey.toString().toLowerCase())) {
                    return Color.ORANGE;
                } else {
                    return new Color(190, 190, 218);
                }
            }
        });
        perfChart.getBars().setSelectionType(BarChartBars.SelectionType.ArrowRight);
        panelSummary.add(perfChart, new CellConstraints().xy(1, 3));

        JPanel performancePanel = createPerformancePanel(perfChart);
        container.add(performancePanel, new CellConstraints().xyw(1, 1, 3));

        JPanel alertPanel = new JPanel();
        alertPanel.setOpaque(false);
        FormLayout alertLayout = new FormLayout("default, 4px, fill:default:grow", "default");
        alertPanel.setLayout(alertLayout);
        panelSummary.add(alertPanel, new CellConstraints().xy(1, 1));

        alertImage = ImageUtils.getImage("icn_alert.png");
        alertLabel = new JLabel(new ImageIcon(alertImage));

        JLabel alertText = new JLabel("9 clients with underperforming campaigns");
        alertText.setFont(FontResolver.getFont(new FontResolver.FontInfo("LucidaGrandeBold", 10, true, true)));
        alertText.setForeground(Color.WHITE);
        alertPanel.add(alertLabel, new CellConstraints().xy(1, 1));
        alertPanel.add(alertText, new CellConstraints().xy(3, 1));
    }

    private JComponent createAuditPanel() {
        JPanel panel = new JPanel();
        panel.setOpaque(false);

        FormLayout layout = new FormLayout("250dlu, 10px, fill:default:grow", "default, 5px, fill:default:grow, 10px, default, 5px, fill:default:grow");
        panel.setLayout(layout);

        JLabel io = getTitleLabel("Unprocessed Insertion Orders");
        panel.add(io, CC.xy(1, 1));

        TableModel ioModel = getIOTableModel();
        JTable ioTable = table(ioModel);
        JScrollPane ioPane = tableScroll(ioTable);
        panel.add(ioPane, CC.xy(1, 3));

        JLabel auditList = getTitleLabel("Audit List");
        panel.add(auditList, CC.xy(1, 5));

        TableModel auditModel = getAuditTableModel();
        JTable auditTable = table(auditModel);
        JScrollPane auditPane = tableScroll(auditTable);
        panel.add(auditPane, CC.xy(1, 7));

        JLabel audit = getTitleLabel("Audit Chart");
        panel.add(audit, CC.xy(3, 1));

        DefaultCategoryDataset model = new DefaultCategoryDataset();
        model.addValue(.50f, "Ordered", "Order 1234, Jan., Yahoo");
        model.addValue(.40f, "Served", "Order 1234, Jan., Yahoo");
        model.addValue(.30f, "Invoiced", "Order 1234, Jan., Yahoo");
        model.addValue(.34f, "Ordered", "Order 2321, Jan., Yahoo");
        model.addValue(.32f, "Served", "Order 2321, Jan., Yahoo");
        model.addValue(.30f, "Invoiced", "Order 2321, Jan., Yahoo");
        model.addValue(.71f, "Ordered", "Order 432, Jan., Yahoo");
        model.addValue(.70f, "Served", "Order 432, Jan., Yahoo");
        model.addValue(.69f, "Invoiced", "Order 432, Jan., Yahoo");
        model.addValue(.90f, "Ordered", "Order 1234, Jan., eBay");
        model.addValue(.85f, "Served", "Order 1234, Jan., eBay");
        model.addValue(.80f, "Invoiced", "Order 1234, Jan., eBay");
        model.addValue(.30f, "Ordered", "Order 1234, Jan., Microsoft");
        model.addValue(.20f, "Served", "Order 1234, Jan., Microsoft");
        model.addValue(.20f, "Invoiced", "Order 1234, Jan., Microsoft");
        model.addValue(.65f, "Ordered", "Order 1234, Jan., Google");
        model.addValue(.63f, "Served", "Order 1234, Jan., Google");
        model.addValue(.54f, "Invoiced", "Order 1234, Jan., Google");

        auditChart = ChartFactory.createBarChart(model, BarChartBars.ValueAxisPosition.Left, true);
        panel.add(auditChart, CC.xywh(3, 3, 1, 5));

        auditChart.getBars().setSteps(10);
        auditChart.getLabel().setMax(220);
        auditChart.getLabel().setSuffix("K");

        return panel;
    }

    private JPanel createPerformancePanel(final BarChart chart) {
        JPanel performancePanel = new JPanel();
        performancePanel.setOpaque(false);
        FormLayout layout = new FormLayout("default, right:default:grow(0.35), default:grow(0.65)", "default");
        performancePanel.setLayout(layout);

        performancePanel.setBorder(BorderFactory.createCompoundBorder(
                BorderFactory.createMatteBorder(1, 0, 1, 0, new Color(60, 60, 60)),
                BorderFactory.createEmptyBorder(5, 5, 3, 5)
        ));

        JLabel label = new JLabel("PERFORMANCE");
        label.setForeground(Color.WHITE);
        label.setFont(FontResolver.getFont(new FontResolver.FontInfo("LucidaGrandeBold", 12, true, true)));
        performancePanel.add(label, CC.xy(1, 1));

        JComboBox sortCombo = new JComboBox();
        sortCombo.setUI(new MiniBlackComboUI());
        performancePanel.add(sortCombo, CC.xy(2, 1));

        DefaultComboBoxModel model = new DefaultComboBoxModel();
        sortCombo.setModel(model);
        model.addElement(new SortOption("Name (Asc)", new DefaultComparator(true), BarChartBars.SortMode.ColumnName, -1));
        model.addElement(new SortOption("Name (Desc)", new DefaultComparator(false), BarChartBars.SortMode.ColumnName, -1));
        model.addElement(new SortOption("Performance (Asc)", new DefaultComparator(true), BarChartBars.SortMode.RowValue, 0));
        model.addElement(new SortOption("Performance (Desc)", new DefaultComparator(false), BarChartBars.SortMode.RowValue, 0));

        sortCombo.setSelectedIndex(2);

        sortCombo.addItemListener(new ItemListener() {
            public void itemStateChanged(ItemEvent e) {
                SortOption option = (SortOption) e.getItem();
                chart.getBars().setSort(option.getComparator(), option.getSortMode(), option.getSortRow());
            }
        });

        return performancePanel;
    }

    protected void initializeComponents(Container container) {
        perfChart.setModel(perfModel);
    }


    protected void attachListenersToComponents(Container container) {
        perfChart.addSelectionListener(new BarChartSelectionListener() {
            public void selectionChanged(Object row) {
                DefaultCategoryDataset sideModel = campaignData.get(row);
                if (sideModel == null) {
                    sideModel = createNewDataset();
                    campaignData.put((Comparable) row, sideModel);
                }

                if (panel2 != null) formComponent.remove(panel2);
                campaignChart = ChartFactory.createBarChart(sideModel, true);
                campaignChart.getBars().setColorResolver(new ColorResolver() {
                    public Color getColor(Comparable rowKey, Comparable columnKey, int rowIndex, float value) {
                        if (value < 0) return Color.RED;
                        return Color.GREEN;
                    }
                });

                panel2 = new JPanel();
                panel2.setOpaque(false);
                FormLayout layout = new FormLayout("fill:default:grow", "default, 4dlu, fill:default:grow");
                panel2.setLayout(layout);

                JPanel labelPanel = createNestedChartHeader(campaignChart, row.toString().toUpperCase());

                panel2.add(labelPanel, new CellConstraints().xy(1, 1));
                panel2.add(campaignChart, new CellConstraints().xy(1, 3));
                panel2.setBorder(BorderFactory.createEmptyBorder(5, 0, 0, 0));

                formComponent.add(panel2, new CellConstraints().xy(3, 3));
                formComponent.revalidate();

                campaignChart.startBuildAnimation();
            }
        });
    }

    private DefaultCategoryDataset createNewDataset() {
        DefaultCategoryDataset sideModel = new DefaultCategoryDataset();

        int campaigns = RandomUtils.getRandomInt(4, 80);
        for (int i = 0; i < campaigns; i++) {
            float value = RandomUtils.getRandomFloat();
            int negative = RandomUtils.getRandomInt(1, 20);
            if (negative < 4) value *= -1;
            sideModel.addValue(value, "Row", "ID " + campaignIdCounter++);
        }

        return sideModel;
    }

    private JPanel createNestedChartHeader(final BarChart chart, String title) {
        JPanel headerPanel = new JPanel();
        headerPanel.setOpaque(false);

        FormLayout layout = new FormLayout("default, 5px, default, right:default:grow", "default");
        headerPanel.setLayout(layout);

        JLabel label = new JLabel("CAMPAIGNS:");
        label.setForeground(new Color(140, 140, 140));
        label.setFont(FontResolver.getFont(new FontResolver.FontInfo("LucidaGrandeBold", 12, true, true)));
        headerPanel.add(label, CC.xy(1, 1));

        JLabel clientLabel = new JLabel(title);
        clientLabel.setForeground(Color.WHITE);
        clientLabel.setFont(FontResolver.getFont(new FontResolver.FontInfo("LucidaGrandeBold", 12, true, true)));
        headerPanel.add(clientLabel, CC.xy(3, 1));

        JComboBox sortCombo = new JComboBox();
        sortCombo.setUI(new MiniBlackComboUI());
        headerPanel.add(sortCombo, CC.xy(4, 1));

        DefaultComboBoxModel model = new DefaultComboBoxModel();
        sortCombo.setModel(model);
        model.addElement(new SortOption("Campaign # (Asc)", new DefaultComparator(true), BarChartBars.SortMode.ColumnName, -1));
        model.addElement(new SortOption("Campaign # (Desc)", new DefaultComparator(false), BarChartBars.SortMode.ColumnName, -1));
        model.addElement(new SortOption("Performance (Asc)", new DefaultComparator(true), BarChartBars.SortMode.RowValue, 0));
        model.addElement(new SortOption("Performance (Desc)", new DefaultComparator(false), BarChartBars.SortMode.RowValue, 0));

        sortCombo.setSelectedIndex(0);

        sortCombo.addItemListener(new ItemListener() {
            public void itemStateChanged(ItemEvent e) {
                SortOption option = (SortOption) e.getItem();
                chart.getBars().setSort(option.getComparator(), option.getSortMode(), option.getSortRow());
            }
        });

        return headerPanel;
    }

    private TableModel getIOTableModel() {
        return new AbstractTableModel() {
            public int getColumnCount() {
                return 3;
            }

            public int getRowCount() {
                return IO_DATA.length;
            }

            public String getColumnName(int column) {
                switch (column) {
                    case 0: return "Id";
                    case 1: return "Vendor";
                    case 2: return "Client";
                    default: return "";
                }
            }

            public Object getValueAt(int rowIndex, int columnIndex) {
                return IO_DATA[rowIndex][columnIndex].toUpperCase();
            }
        };
    }

    private TableModel getAuditTableModel() {
        return new AbstractTableModel() {
            public int getColumnCount() {
                return 4;
            }

            public int getRowCount() {
                return AUDIT_DATA.length;
            }

            public String getColumnName(int column) {
                switch (column) {
                    case 0: return "Audit Date";
                    case 1: return "Vendor";
                    case 2: return "Campaign";
                    case 3: return "Month of Service";
                    default: return "";
                }
            }

            public Object getValueAt(int rowIndex, int columnIndex) {
                return AUDIT_DATA[rowIndex][columnIndex].toUpperCase();
            }
        };
    }

    public String getTitle() {
        return "Digital Dashboard";
    }

    @Override
    public void panelDisplayed() {
        perfChart.startBuildAnimation();
        auditChart.startBuildAnimation();

//        throbAnimator = new Animator(500, 10, Animator.RepeatBehavior.REVERSE, new TimingTargetAdapter() {
//            public void begin() {
//                BufferedImage bi = GraphicsUtilities.createCompatibleImage(alertImage);
//                Graphics2D g2d = bi.createGraphics();
//                g2d.setColor(new Color(0, 0, 0, 0));
//                g2d.fillRect(0, 0, bi.getWidth(), bi.getHeight());
//                g2d.dispose();
//                alertLabel.setIcon(new ImageIcon(bi));
//            }
//
//            public void end() {
//                alertLabel.setIcon(new ImageIcon(alertImage));
//                alertLabel.repaint();
//            }
//
//            public void timingEvent(float v) {
//                BufferedImage bi = GraphicsUtilities.createCompatibleImage(alertImage);
//                Graphics2D g2d = bi.createGraphics();
//                g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, v));
//                g2d.drawImage(alertImage, 0, 0, null);
//                g2d.dispose();
//                alertLabel.setIcon(new ImageIcon(bi));
//                alertLabel.repaint();
//            }
//        });
//        throbAnimator.setStartDirection(Animator.Direction.BACKWARD);
//
//        thread = new ThrobberThread();
//        thread.start();
    }

    protected void dispose() {
        if (thread != null) thread.setCancelled(true);
    }

    private JLabel getTitleLabel(String text) {
        JLabel label = new JLabel(text);
        label.setBorder(BorderFactory.createCompoundBorder(
                BorderFactory.createMatteBorder(1, 0, 1, 0, new Color(60, 60, 60)),
                BorderFactory.createEmptyBorder(5, 5, 3, 5)
        ));
        label.setForeground(Color.WHITE);
        label.setFont(FontResolver.getFont(new FontResolver.FontInfo("LucidaGrandeBold", 12, true, true)));
        return label;
    }

    private void paintGrayGradient(Graphics g, JComponent c, boolean leftBorder, boolean rightBorder) {
        Graphics2D g2d = (Graphics2D) g.create();

        g2d.setPaint(new LinearGradientPaint(0, 0, 0, c.getHeight(),
                new float[] { 0, 0.48f, 0.5f, 1f },
                new Color[] { new Color(119, 119, 117), new Color(75, 75, 75), new Color(31, 33, 32), new Color(73, 73, 73) }
        ));
        g2d.fillRect(0, 0, c.getWidth(), c.getHeight());

        g2d.setColor(new Color(148, 148, 146));
        g2d.drawLine(0, 0, c.getWidth(), 0);
        if (leftBorder) {
            g2d.drawLine(0, 0, 0, c.getHeight());
        }

        g2d.setColor(new Color(0, 0, 0, 32));
        g2d.drawLine(0, c.getHeight() - 1, c.getWidth(), c.getHeight() - 1);
        if (rightBorder) {
            g2d.drawLine(c.getWidth() - 1, 0, c.getWidth() - 1, c.getHeight());
        }

        g2d.dispose();
    }

    public JTable table(TableModel model) {
        JTable table = new JTable(model);
        for (int i = 0; i < table.getColumnCount(); i++) {
            final boolean left = (i > 0);
            final boolean right = (i < table.getColumnCount() - 1);

            table.getColumnModel().getColumn(i).setHeaderRenderer(new TableCellRenderer() {
                private JLabel renderer;

                public Component getTableCellRendererComponent(final JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
                    if (renderer == null) {
                        renderer = new JLabel() {
                            protected void paintComponent(Graphics g) {
                                paintGrayGradient(g, renderer, left, right);
                                super.paintComponent(g);
                            }
                        };
                        renderer.setFont(FontResolver.getFont(new FontResolver.FontInfo("LucidaGrandeBold", 10, true, true)));
                        renderer.setForeground(Color.WHITE);
                        renderer.setBorder(Borders.DLU4_BORDER);
                        renderer.setOpaque(false);
                    }
                    renderer.setText(value.toString().toUpperCase());
                    return renderer;
                }
            });

            table.getColumnModel().getColumn(i).setCellRenderer(new TableCellRenderer() {
                private JLabel renderer;

                public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, final int row, int column) {
                    if (renderer == null) {
                        renderer = new JLabel() {
                            protected void paintComponent(Graphics g) {
                                Graphics2D g2d = (Graphics2D) g.create();

                                Boolean selected = (Boolean) getClientProperty("table.selected");
                                if ((selected != null) && (selected.booleanValue())) {
                                    g2d.setPaint(new LinearGradientPaint(0, 0, 0, getHeight(),
                                            new float[] { 0f, 0.48f, 0.481f, 1f },
                                            new Color[] { new Color(135, 155, 187), new Color(35, 77, 145), new Color(6, 47, 117), new Color(31, 66, 131) }
                                    ));
                                } else {
                                    Integer row = (Integer) getClientProperty("table.row");
                                    if (row == null) row = 0;
                                    Color c = (row % 2 == 0) ? new Color(98, 100, 99) : new Color(83, 85, 84);
                                    g2d.setColor(c);
                                }

                                g2d.fillRect(0, 0, getWidth(), getHeight());

                                if (left) {
                                    g2d.setColor(new Color(148, 148, 146));
                                    g2d.drawLine(0, 0, 0, getHeight());
                                }

                                g2d.dispose();

                                super.paintComponent(g);
                            }
                        };
                        renderer.setFont(FontResolver.getFont(new FontResolver.FontInfo("LucidaGrandeBold", 8, true, true)));
                        renderer.setForeground(Color.WHITE);
                        renderer.setBorder(Borders.DLU4_BORDER);
                        renderer.setOpaque(false);
                    }
                    renderer.putClientProperty("table.selected", isSelected);
                    renderer.putClientProperty("table.row", row);
                    renderer.setText(value.toString());
                    return renderer;
                }
            });
        }
        table.setShowGrid(false);
        table.setRowHeight(20);
        table.setRowMargin(0);
        table.setIntercellSpacing(new Dimension(0, 0));
        return table;
    }

    public JScrollPane tableScroll(JTable view) {
        JScrollPane pane = new JScrollPane(view);

        pane.getVerticalScrollBar().setUI(new CandylandScrollbarUI());
        pane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
        pane.setCorner(ScrollPaneConstants.UPPER_RIGHT_CORNER, new JPanel() {
            protected void paintComponent(Graphics g) {
                paintGrayGradient(g, this, false, true);
            }
        });

        int height = view.getColumnModel().getColumn(0).getHeaderRenderer().getTableCellRendererComponent(null, "Foo", false, false, 0, 0).getPreferredSize().height;
        height += view.getRowCount() * view.getRowHeight() + 2; // 2 pixels for the border
        pane.setPreferredSize(new Dimension((int) (pane.getPreferredSize().getWidth() * 1.2f), height));
        pane.setMaximumSize(pane.getPreferredSize());

        pane.setOpaque(false);
        view.setOpaque(false);
        pane.getViewport().setOpaque(false);
        pane.setBorder(null);
        view.setBorder(null);

        return pane;
    }

    private class ThrobberThread extends Thread {
        private boolean cancelled = false;

        public ThrobberThread() {
            setDaemon(true);
        }

        public boolean isCancelled() {
            return cancelled;
        }

        public void setCancelled(boolean cancelled) {
            this.cancelled = cancelled;
        }

        public void run() {
            try {
                Thread.sleep(2500);
            } catch (InterruptedException e) {}

            while (!isCancelled()) {
                throbAnimator.setStartDirection(Animator.Direction.BACKWARD);
                throbAnimator.start();
                try {
                    Thread.sleep(15000);
                } catch (InterruptedException e) {}
            }
        }
    }

    public class SortOption {
        private String name;
        private Comparator comparator;
        private BarChartBars.SortMode sortMode;
        private int sortRow = -1;

        public SortOption(String name, Comparator comparator, BarChartBars.SortMode sortMode, int sortRow) {
            this.sortRow = sortRow;
            this.sortMode = sortMode;
            this.name = name;
            this.comparator = comparator;
        }

        public Comparator getComparator() {
            return comparator;
        }

        public String getName() {
            return name;
        }

        public BarChartBars.SortMode getSortMode() {
            return sortMode;
        }

        public int getSortRow() {
            return sortRow;
        }

        public String toString() {
            return name;
        }
    }

    public class DefaultComparator implements Comparator {
        private boolean asc = true;

        public DefaultComparator(boolean ascending) {
            this.asc = ascending;
        }

        public int compare(Object o1, Object o2) {
            Comparable c1 = (Comparable) o1;
            Comparable c2 = (Comparable) o2;

            return (asc) ? c1.compareTo(c2) : c2.compareTo(c1);
        }
    }
}
